package cn.b.sky.shop.activiti.controller;

import cn.b.sky.common.enums.DwzCallbackType;
import cn.b.sky.shop.activiti.constans.GlobalDefine;
import cn.b.sky.shop.activiti.model.SkyService;
import cn.b.sky.shop.activiti.model.NodeDTO;
import cn.b.sky.shop.activiti.model.ProcessModelCategory;
import cn.b.sky.shop.activiti.model.ProcessModelParam;
import cn.b.sky.shop.activiti.service.DeploymentNodeService;
import cn.b.sky.shop.activiti.service.SkyServiceService;
import cn.b.sky.shop.activiti.service.ProcessModelCategoryService;
import cn.b.sky.shop.activiti.service.ProcessParamService;
import cn.b.sky.shop.activiti.utils.CustomProcessDiagramGenerator;
import cn.b.sky.common.constants.SystemConstant;
import cn.b.sky.common.exception.ImeException;
import cn.b.sky.common.model.DWZResult;
import cn.b.sky.form.model.BusinessField;
import cn.b.sky.form.service.BusinessFieldService;
import cn.b.sky.form.service.BusinessTypeService;
import cn.b.sky.user.SecurityContextUtil;
import cn.b.sky.user.controller.SecurityController;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.*;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.impl.persistence.entity.ModelEntity;
import org.activiti.engine.repository.Deployment;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import java.awt.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * <strong>Title : ModelController<br>
 * </strong> <strong>Package : cn.b.sky.shop.activiti.controller<br>
 * </strong> <strong>Description : </strong> 流程模型controller<br>
 * <strong>Create on : 2014-10-15上午11:28:32<br>
 * </strong>
 * <p>
 * <strong>Copyright (c) 2014</strong>
 * <p>
 * 
 * @author lidapeng<br>
 * @version <strong>v1.0.0</strong><br>
 * <br>
 *          <strong>修改历史:</strong><br>
 *          修改人 | 修改日期 | 修改描述<br>
 *          -------------------------------------------<br>
 * <br>
 * <br>
 */
@Controller
@RequestMapping(value = "/model/*")
public class ModelController extends SecurityController {

  private static final Logger LOGGER = LoggerFactory.getLogger(ModelController.class);

  @Autowired
  private RepositoryService repositoryService;

  @Resource
  private ProcessModelCategoryService processModelCategoryService;

  @Resource
  private SkyServiceService skyService;

  @Resource
  private DeploymentNodeService deploymentNodeService;

  @Resource
  private ProcessParamService processParamService;
  
  @Resource
  BusinessTypeService businessTypeService;
  @Resource
  BusinessFieldService businessFieldService;
  
  /**
   * 模型列表
   */
  @RequestMapping("modelList")
  public String toModellist(Model model, String navTabId, String name) {
    List<ProcessModelCategory> pmc = processModelCategoryService.getProcessModelCategoryTree(this.getSiteCode());
    model.addAttribute("processModel", pmc);
    //模型的名称用来确定焦点
    model.addAttribute("modelName", name);
    model.addAttribute(NAVTABID, navTabId);
    return "engine/model_list";
  }

  @RequestMapping("addModelDetailCategory")
  public String addModelDetailCategory(Model model, String navTabId) {
    model.addAttribute("businessTypes", businessTypeService.selectBusinessTypes());
    return "engine/addmodelcategory";
  }

  @RequestMapping("addModel")
  public String addModel(Model model, String category, String navTabId) {
    if (StringUtils.isBlank(category)) {
      throw ImeException.newException(SystemConstant.PARAMETERNULL);
    }
    model.addAttribute(NAVTABID, navTabId);
    model.addAttribute("category", category);
    return "engine/addmodel";
  }

  @RequestMapping("viewModelCategory")
  public String viewModelCategory(Model model, String category, String navTabId) {
    ProcessModelCategory pmc = processModelCategoryService.getProcessModelCategoryByCategory(category,
        this.getSiteCode());
    model.addAttribute(NAVTABID, navTabId);
    model.addAttribute("processModelCategory", pmc);
    model.addAttribute("businessTypes", businessTypeService.selectBusinessTypes());
    return "engine/viewmodelcategory";
  }

  @RequestMapping("getModelDetail")
  public String getModelDetail(Model model, String id) throws JsonProcessingException, IOException {
    if (StringUtils.isBlank(id)) {
      throw ImeException.newException(SystemConstant.PARAMETERNULL);
    }
    ModelEntity modelEntity = (ModelEntity) repositoryService.createModelQuery().modelId(id).singleResult();

    // 没有流程图
    if (modelEntity.getEditorSourceExtraValueId() != null) {
      model.addAttribute("hasModelExtra", true);
      byte[] bytes = this.repositoryService.getModelEditorSource(id);
      JsonNode modelNode = new ObjectMapper().readTree(bytes);
      BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(modelNode);
      Map<String, GraphicInfo> graphicInfoMap = bpmnModel.getLocationMap();

      Point point = CustomProcessDiagramGenerator.getMinXAndMinY(bpmnModel);

      int minX = point.x;
      int minY = point.y;
      minX = minX <= 5 ? 5 : minX;
      minY = minY <= 5 ? 5 : minY;
      minX -= 5;
      minY -= 5;

      List<NodeDTO> nodeDtos = new ArrayList<NodeDTO>();
      for (Entry<String, GraphicInfo> entry : graphicInfoMap.entrySet()) {
        String key = entry.getKey();
        GraphicInfo graphicInfo = entry.getValue();
        graphicInfo.setX(graphicInfo.getX() - minX);
        graphicInfo.setY(graphicInfo.getY() - minY);
        FlowElement flowElement = bpmnModel.getFlowElement(key);
        NodeDTO dto = convertNodeDto(graphicInfo, flowElement, key);
        nodeDtos.add(dto);
      }

      model.addAttribute("nodeDto", nodeDtos);
    }
    model.addAttribute("bpmnModel", modelEntity);
    model.addAttribute("userLanguage", SecurityContextUtil.getLanguageCode());
    return "engine/modelDtail";
  }

  private NodeDTO convertNodeDto(GraphicInfo graphicInfo, FlowElement flowElement, String id) {
    NodeDTO nodeDto = new NodeDTO();
    nodeDto.setX((int) graphicInfo.getX());
    nodeDto.setY((int) graphicInfo.getY());
    nodeDto.setWidth((int) graphicInfo.getWidth());
    nodeDto.setHeight((int) graphicInfo.getHeight());

    if (flowElement instanceof UserTask) {
      nodeDto.setType(GlobalDefine.WorkFlow.USAERTASK);
    }
    if(flowElement instanceof StartEvent){
      nodeDto.setType(GlobalDefine.WorkFlow.STARTEVENT);
    }
    nodeDto.setId(id);
    nodeDto.setName(flowElement.getName());
    return nodeDto;
  }

  /**
   * 流程变量
   */
  @RequestMapping("paramList")
  public String toParamlist(Model model) {
    List<org.activiti.engine.repository.Model> list = repositoryService.createModelQuery().list();
    model.addAttribute("processModel", list);

    return "engine/param_list";
  }

  /**
   * 
   * <strong>Title : getModelEditorSourceExtra<br></strong>
   * <strong>Description : </strong>获取流程模型图<br> 
   * <strong>Create on : 2014-10-16下午3:53:15<br></strong>
   * <p> void
   * @throws 
   * @author lidapeng<br>
   * @version <strong>v1.0.0</strong><br>
   * <br>
   * <strong>修改历史:</strong><br>
   * 修改人  | 修改日期  | 修改描述<br>
   * -------------------------------------------<br>
   * <br>
   * <br>
   */
  @RequestMapping("getModelEditorSourceExtra")
  public void getModelEditorSourceExtra(String modelId, Model model, final HttpServletResponse response) {
    response.setContentType("image/png");
    response.setHeader("Pragma", "No-cache");
    response.setHeader("Cache-Control", "no-cache");
    response.setDateHeader("Expire", 0);
    try {
      ServletOutputStream sout = response.getOutputStream();
      byte[] buf = repositoryService.getModelEditorSourceExtra(modelId);
      sout.write(buf);
    } catch (IOException e) {
      LOGGER.error("{}", e);
    }
  }

  /**
   * 
  * @Title: isExitsModel 
  * @Description: 校验流程模型是否已经存在
  * @param key
  * @param model
  * @return DWZResult    返回类型 
  * @throws
   */
  @RequestMapping("isExitsModel")
  @ResponseBody
  public boolean isExitsModel(String key, Model model) {
    if (!StringUtils.isBlank(key)) {
      long count = repositoryService.createModelQuery().modelKey(key).count();
      if (count > 0) {
        return false;
      }
    }
    return true;
  }

  @RequestMapping("isExitsModelCategory")
  @ResponseBody
  public boolean isExitsModelCategory(String category, Model model) {
    if (!StringUtils.isBlank(category)) {
      Integer count = processModelCategoryService.checkeCategoryIsExits(category, this.getSiteCode());
      if (count > 0) {
        return false;
      }
    }
    return true;
  }

  @RequestMapping("saveModelCategory")
  @ResponseBody
  public DWZResult saveModelCategory(ProcessModelCategory category, String navTabId, HttpServletRequest request,
      Model model) {
    if (category == null || StringUtils.isBlank(category.getCategory())) {
      return ajaxError(getImeViewMessageI18n(SecurityContextUtil.getLanguageCode(), SystemConstant.PARAMETERNULL));
    }
    category.setTenantId(this.getSiteCode());
    processModelCategoryService.saveModelCategory(category);
    String forwardUrl = getForwardUrl(request) + "/model/modelList?navTabId=" + navTabId;
    return navTabAjaxDone(navTabId, null, DwzCallbackType.FORWARD, forwardUrl, null, category);
  }

  @RequestMapping("modifyModelCategory")
  @ResponseBody
  public DWZResult modifyModelCategory(ProcessModelCategory category,String navTabId, Model model) {
    if (category == null || StringUtils.isBlank(category.getCategory())) {
      return ajaxError(getImeViewMessageI18n(SecurityContextUtil.getLanguageCode(), SystemConstant.PARAMETERNULL));
    }
    category.setTenantId(this.getSiteCode());
    processModelCategoryService.modifyModelCategory(category);
    return navTabAjaxDone(navTabId, null, null, null, null,null);
  }

  /**
   * 保存模型
   */
  @RequestMapping("saveModel")
  @ResponseBody
  public DWZResult saveModel(String name, String category, String description, String navTabId,
      HttpServletRequest request) {
    if (StringUtils.isBlank(category)) {
      return ajaxError(getImeViewMessageI18n(SecurityContextUtil.getLanguageCode(), SystemConstant.PARAMETERNULL));
    }
    try {
      ObjectMapper objectMapper = new ObjectMapper();
      ObjectNode editorNode = objectMapper.createObjectNode();
      editorNode.put("id", "canvas");
      editorNode.put("resourceId", "canvas");
      ObjectNode stencilSetNode = objectMapper.createObjectNode();
      stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
      editorNode.put("stencilset", stencilSetNode);
      ModelEntity modelData = (ModelEntity) repositoryService.newModel();

      ObjectNode modelObjectNode = objectMapper.createObjectNode();
      modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, name);
      modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);
      //将""或者Null 转换为 ""
      description = StringUtils.defaultString(description);
      modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
      modelData.setMetaInfo(modelObjectNode.toString());
      modelData.setName(name);
      modelData.setCategory(category);

      modelData.setTenantId(getSiteCode());

      // 保存模型
      repositoryService.saveModel(modelData);
      repositoryService.addModelEditorSource(modelData.getId(), editorNode.toString().getBytes("utf-8"));
      String forwardUrl = getForwardUrl(request) + "/model/modelList?navTabId=" + navTabId;
      return navTabAjaxDone(null, null, DwzCallbackType.FORWARD, forwardUrl, null, modelData);
    } catch (Exception e) {
      LOGGER.error("创建模型失败：", e);
      return ajaxError(getImeViewMessageI18n(SecurityContextUtil.getLanguageCode(), "failedOperation.M"));
    }
  }

  @RequestMapping("toImportModel")
  public String toImportModel(Model model, String category, String navTabId) {
    if (StringUtils.isBlank(category)) {
      throw ImeException.newException(SystemConstant.PARAMETERNULL);
    }
    model.addAttribute("category", category);
    model.addAttribute(NAVTABID, navTabId);
    return "engine/importmodel";
  }

  @RequestMapping("importModel")
  @ResponseBody
  public DWZResult importModel(@RequestParam("modelfile") MultipartFile modelfile, String navTabId, String category,
                               HttpServletRequest request, Model model) {
    if (StringUtils.isBlank(category) || modelfile.isEmpty()) {
      LOGGER.error("文件为空，模型类型为空！");
      return ajaxError(getImeViewMessageI18n(SecurityContextUtil.getLanguageCode(), SystemConstant.PARAMETERNULL));

    }
    String fileName = modelfile.getOriginalFilename();
    if (!(fileName.endsWith(".bpmn20.xml") || fileName.endsWith(".bpmn"))) {
      LOGGER.error("<h2>不能导入文件</h2><br/>仅支持.bpmn或.bpmn20.xml类型的文件！");
      return ajaxError(getImeViewMessageI18n(SecurityContextUtil.getLanguageCode(), "failedOperation.M"));
    }
    try {
      BpmnXMLConverter xmlConverter = new BpmnXMLConverter();
      XMLInputFactory xif = XMLInputFactory.newInstance();
      InputStreamReader in = new InputStreamReader(new ByteArrayInputStream(modelfile.getBytes()), "UTF-8");
      XMLStreamReader xtr = xif.createXMLStreamReader(in);
      BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr);
      xmlConverter.convertToBpmnModel(xtr);
      if (bpmnModel.getMainProcess() == null || bpmnModel.getMainProcess().getId() == null) {
        LOGGER.error("导入失败，请确认文件中包含有效的BPMN定义！");
        return ajaxError(getImeViewMessageI18n(SecurityContextUtil.getLanguageCode(), "failedOperation.M"));
      } else {
        if (bpmnModel.getLocationMap().size() == 0) {
          LOGGER.error("校验失败，请确认文件中包含BPMN DI信息!");
          return ajaxError(getImeViewMessageI18n(SecurityContextUtil.getLanguageCode(), "failedOperation.M"));
        } else {
          //系统导出的流程标识含有流程模型类型以@_@分割
          String[] processKeys = bpmnModel.getMainProcess().getId().split(SystemConstant.MIDDLE_SPLIT);
          if(processKeys.length == 2){
            bpmnModel.getMainProcess().setId(processKeys[1]);
          }
          String processName = bpmnModel.getMainProcess().getName();
          String processKey = bpmnModel.getMainProcess().getId();
          if (StringUtils.isBlank(processName)) {
            processName = processKey;
          }
          ModelEntity modelData = (ModelEntity) repositoryService.newModel();
          ObjectNode modelObjectNode = new ObjectMapper().createObjectNode();
          modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, processName);
          modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);
          modelData.setMetaInfo(modelObjectNode.toString());
          modelData.setName(processName);
          modelData.setCategory(category);
          modelData.setKey(processKey);
          modelData.setTenantId(getSiteCode());
          repositoryService.saveModel(modelData);

          BpmnJsonConverter jsonConverter = new BpmnJsonConverter();
          ObjectNode editorNode = jsonConverter.convertToJson(bpmnModel);

          repositoryService.addModelEditorSource(modelData.getId(), editorNode.toString().getBytes("utf-8"));
          String forwardUrl = getForwardUrl(request) + "/model/modelList.htm?navTabId=" + navTabId;
          return dialogAjaxDone(navTabId, null, forwardUrl, modelData);
        }
      }
    } catch (Exception e) {
      String errorMsg = e.getMessage().replace(System.getProperty("line.separator"), "<br/>");
      LOGGER.error(errorMsg, e);
      return ajaxError(getImeViewMessageI18n(SecurityContextUtil.getLanguageCode(), "failedOperation.M"));
    }

  }

  /**
   * 根据Model部署流程
   */
  @ResponseBody
  @RequestMapping("deploy")
  public DWZResult deployModel(String modelId) {

    ModelEntity modelData = (ModelEntity) repositoryService.getModel(modelId);
    if (modelData.getEditorSourceExtraValueId() == null) {
      throw ImeException.newException("process_noimg.C,notDeploy.M");
    }
    try {
      ObjectNode modelNode = (ObjectNode) new ObjectMapper().readTree(this.repositoryService
          .getModelEditorSource(modelData.getId()));
      byte[] bpmnBytes = null;

      BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);

      bpmnBytes = new BpmnXMLConverter().convertToXML(model);

      String processName = modelData.getName() + ".bpmn20.xml";

      Deployment deployment = this.repositoryService.createDeployment().name(modelData.getName())
          .tenantId(modelData.getTenantId()).addString(processName, new String(bpmnBytes, "UTF-8")).deploy();

      /*List<ProcessDefinition> list = this.repositoryService.createProcessDefinitionQuery()
          .deploymentId(deployment.getId()).list();
      for (ProcessDefinition processDefinition : list) {
        exportDiagramToFile(processDefinition);
      }*/
      modelData.setDeploymentId(deployment.getId());
      repositoryService.saveModel(modelData);
      //发布流程时复制节点配置信息
      deploymentNodeService.copyModelNodeToDeploymentNode(modelId, this.getSiteCode(), deployment.getId());
      return ajaxDone(null);
    } catch (Exception e) {
      LOGGER.error("根据模型部署流程失败：modelId={}", modelId, e);
      return ajaxError(getImeViewMessageI18n(SecurityContextUtil.getLanguageCode(), "failedOperation.M"));
    }
  }

  /**
   * 导出model的xml文件
   */
  @RequestMapping("exportModel")
  public void exportModel(String modelId, HttpServletResponse response) {

    ModelEntity modelData = (ModelEntity) repositoryService.getModel(modelId);
    if (modelData.getEditorSourceExtraValueId() == null) {
      throw ImeException.newException("process_noimg.C,notExport.M");
    }
    try {
      BpmnJsonConverter jsonConverter = new BpmnJsonConverter();
      JsonNode editorNode = new ObjectMapper().readTree(repositoryService.getModelEditorSource(modelData.getId()));
      BpmnModel bpmnModel = jsonConverter.convertToBpmnModel(editorNode);
      String filename = bpmnModel.getMainProcess().getId() + ".bpmn20.xml";
      //设置导出的流程id为category+@_@+id
      bpmnModel.getMainProcess().setId(modelData.getCategory() + SystemConstant.MIDDLE_SPLIT + bpmnModel.getMainProcess().getId());
      response.setContentType("application/octet-stream");
      response.addHeader("Content-Disposition", "attachment; filename=" + filename);
      BpmnXMLConverter xmlConverter = new BpmnXMLConverter();
      byte[] bpmnBytes = xmlConverter.convertToXML(bpmnModel);

      ByteArrayInputStream in = new ByteArrayInputStream(bpmnBytes);
      response.setBufferSize(bpmnBytes.length);
      IOUtils.copy(in, response.getOutputStream());

      response.flushBuffer();
    } catch (Exception e) {
      LOGGER.error("导出model的xml文件失败：modelId={}", modelId, e);
      throw ImeException.newException("failedOperation.M");
    }
  }

  @RequestMapping("deleteModel")
  @ResponseBody
  public DWZResult deleteModel(String modelId, String navTabId, HttpServletRequest request) {
    if (StringUtils.isBlank(modelId)) {
      return ajaxError(getImeViewMessageI18n(SecurityContextUtil.getLanguageCode(), SystemConstant.PARAMETERNULL));
    }
    repositoryService.deleteModel(modelId);
    String forwardUrl = getForwardUrl(request) + "/model/modelList?navTabId=" + navTabId;
    return navTabAjaxDone(null, null, DwzCallbackType.FORWARD, forwardUrl, null, null);
  }

  @RequestMapping("properties/{modelId}")
  @ResponseBody
  public JSONObject getModelProperties(@PathVariable("modelId") String modelId) {

    List<SkyService> services = this.skyService.searchServiceByModelId(modelId);

    List<ProcessModelParam> params=new ArrayList<ProcessModelParam>();
    
    org.activiti.engine.repository.Model model=repositoryService.getModel(modelId);
    ProcessModelCategory pmc = processModelCategoryService.getProcessModelCategoryByCategory(model.getCategory(),
        this.getSiteCode());
    if(StringUtils.isBlank(pmc.getBusinessType())){
      params = this.processParamService.getProcessModeParamByModelId(modelId);
    }else{
      List<BusinessField> fields=businessFieldService.selectBusinessFields(getSiteCode(), pmc.getBusinessType());
      for(BusinessField field:fields){
        ProcessModelParam param=new ProcessModelParam();
        param.setCode(field.getDbField());
        param.setName(field.getFieldLabel());
        params.add(param);
      }
    }

    JSONObject object = new JSONObject();
    object.put("services", services);
    object.put("params", params);
    return object;
  }
}
